﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Threading;

using VDataAdapter.EventManagement;
using VDataAdapter.TestPreparation;
using VDataAdapter.WorkerManagement;
using VDataAdapter.Communication;
using VDataAdapter.BlockingQueue;

namespace VDataAdapter.QueueManagement
{
    [TestClass]
    public class TestEventHandlerWithQueue
    {
        [TestMethod]
        public void TestFirstTimeEventHappen()
        {
            string TEST_DATA = TestDataFolder.GetTestDataFolder();
            PriceServerObjectFile reader = new PriceServerObjectFile();
            Object[] list = reader.ReadFromFile(TEST_DATA + "\\Event\\GetFirstTime.test");

            //TODO: Clean this, at least add type check
            // this is stupid, but debug and read content of the list to know why
            // too sleepy to do it clean now
            FirstTimeEvent e = (FirstTimeEvent)list[2];
            Queue<String> expected = (Queue<string>)list[3];

            //Create the eQueue that will connect the price server event handler and the workers
            SimpleEventQueue eQueue = new SimpleEventQueue();

            //Start the enqueuer thread
            int expectedWaitTime = 5;
            EnqueuerThread enqueuer = new EnqueuerThread(eQueue, e, expectedWaitTime);
            Thread enQueuerThread = new Thread(enqueuer.EnQueuer);
            enQueuerThread.Start();

            // setting up
            DataSource dataSource = new DataSource();
            CommunicationDataQueue queue = new CommunicationDataQueue(); // this is the queue worker will write to
            VDataCommunicator comm = new VDataCommunicator(dataSource, queue);

            // in this test firstTimeWorker signal no one
            FirstTimeWorker worker = new FirstTimeWorker(Thread.CurrentThread.Name,null, comm, eQueue);

            worker.StartWork(null); // do not need data now

            DateTime startTime = DateTime.Now;

            // Thread.Sleep(expectedWaitTime * 1000); // wait for event to be enqueue


            worker.WaitToStop();

            if (worker.State == WorkerState.Error)
            {
                Assert.Fail("FirstTimeWorker fails to finish work correctly: " + worker.WorkException.ToString());
            }
            // worker has to wait for expectedWaitTime seconds

            TimeSpan s = DateTime.Now - startTime;
            Assert.IsTrue(s.TotalMilliseconds > expectedWaitTime * 1000 - 10, "Expected is: " + expectedWaitTime + "seconds " + ". Actual is: " + s.TotalMilliseconds + "miliseconds");

            if (worker.State != WorkerState.Error)
            {
                Queue<string> actual = ToStringQueue(queue);

                Assert.AreEqual(expected.Count, actual.Count);

                // cannot use this
                //while (actual.Count > 0)
                //{
                //    Assert.AreEqual(expected.Dequeue(), actual.Dequeue());                     
                //}
            }
        }

        [TestMethod]
        public void FirstTimeEventWillDequeueAndWorkOnEvent()
        {
            string TEST_DATA = TestDataFolder.GetTestDataFolder();
            PriceServerObjectFile reader = new PriceServerObjectFile();
            Object[] list = reader.ReadFromFile(TEST_DATA + "\\Event\\GetFirstTime.test");

            //TODO: Clean this, at least add type check
            // this is stupid, but debug and read content of the list to know why
            // too sleepy to do it clean now
            FirstTimeEvent e = (FirstTimeEvent)list[2];
            Queue<String> expected = (Queue<string>)list[3];

            //Create the eQueue that will connect the price server event handler and the workers
            SimpleEventQueue eQueue = new SimpleEventQueue();

            //Start the enqueuer thread
            int expectedWaitTime = 1;
            EnqueuerThread enqueuer = new EnqueuerThread(eQueue, e, expectedWaitTime);
            Thread enQueuerThread = new Thread(enqueuer.EnQueuer);
            enQueuerThread.Start();

            // setting up
            DataSource dataSource = new DataSource();
            CommunicationDataQueue queue = new CommunicationDataQueue(); // this is the queue worker will write to
            VDataCommunicator comm = new VDataCommunicator(dataSource, queue);

            // in this test firstTimeWorker signal no one
            FirstTimeWorker worker = new FirstTimeWorker(Thread.CurrentThread.Name, null, comm, eQueue);

            worker.IsHardWorking = true;

            worker.StartWork(null); // do not need data now

            Thread.Sleep(expectedWaitTime * 1000); // wait for event to be enqueue

            worker.IsHardWorking = false;

            worker.WaitToStop();

            if (worker.State == WorkerState.Error)
            {
                Assert.Fail("FirstTimeWorker fails to finish work correctly: " + worker.WorkException.ToString());
            }
           
            if (worker.State != WorkerState.Error)
            {
                Queue<string> actual = ToStringQueue(queue);

                while (actual.Count > 0)
                {
                    TimeInVariantAsserter.AreEqual(expected.Dequeue(), actual.Dequeue());
                }

                Assert.AreEqual(expected.Count, actual.Count);
            }
        }

        private static Queue<string> ToStringQueue(CommunicationDataQueue queue)
        {
            Queue<string> result = new Queue<string>();
            CommunicationData data;

            while (queue.getQueueSize() > 0)
            {
                data = queue.get();
                result.Enqueue(queue.GetQueueItemString(data));
            }
            return result;
        }

    }

    public class EnqueuerThread
    {
        private IEventStoteredQueue _eQueue;
        private FirstTimeEvent _firstTimeEvent;
        private int _waitSeconds;

        public EnqueuerThread(IEventStoteredQueue eQueue, FirstTimeEvent firstTimeEvent, int waitSeconds)
        {
            _eQueue = eQueue;
            _firstTimeEvent = firstTimeEvent;
            _waitSeconds = waitSeconds;
        }

        //This method will be ran on a separate thread
        //Wait for 5s then add the event to the queue
        public void EnQueuer()
        {
            //Sleep 5s
            Thread.Sleep(_waitSeconds * 1000);

            //Add event to queue
            _eQueue.PutFirstTimeEvent(_firstTimeEvent);

        }
    }
}
